Passed
Branch v8.x (6e8118)
by Rafael S.
02:42
created

wavio.js ➔ truncateSamples   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 8
c 1
b 0
f 0
nc 5
dl 0
loc 13
rs 9.3333
nop 1
1
/*
2
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview A class to read and write wav data.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import riffChunks from '../vendor/riff-chunks.js';
31
import {pack, packStringTo, packTo, packString,} from '../vendor/byte-data.js';
32
33
// @type {BufferIO}  
34
import BufferIO from './bufferio.js';
35
// @type {WavStruct}
36
import WavStruct from './wavstruct.js';
37
38
/**
39
 * A class to read and write wav data.
40
 * @extends WavStruct
41
 */
42
export default class WavIO extends WavStruct {
43
44
  constructor() {
45
    super();
46
    /**
47
     * @type {!Object}
48
     * @private
49
     */
50
    this.uInt16_ = {bits: 16, be: false};
51
    /**
52
     * @type {!Object}
53
     * @private
54
     */
55
    this.uInt32_ = {bits: 32, be: false};
56
    /**
57
     * The bit depth code according to the samples.
58
     * @type {string}
59
     */
60
    this.bitDepth = '0';
61
    /**
62
     * @type {!Object}
63
     * @private
64
     */
65
    this.dataType = {};
66
    this.io = new BufferIO();
67
  }
68
69
  /**
70
   * Return the bytes of the 'bext' chunk.
71
   * @return {!Array<number>} The 'bext' chunk bytes.
72
   * @private
73
   */
74
  getBextBytes_() {
75
    /** @type {!Array<number>} */
76
    let bytes = [];
77
    this.enforceBext_();
78
    if (this.bext.chunkId) {
79
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
80
      bytes = bytes.concat(
81
        packString(this.bext.chunkId),
82
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
83
        this.io.writeString_(this.bext.description, 256),
84
        this.io.writeString_(this.bext.originator, 32),
85
        this.io.writeString_(this.bext.originatorReference, 32),
86
        this.io.writeString_(this.bext.originationDate, 10),
87
        this.io.writeString_(this.bext.originationTime, 8),
88
        pack(this.bext.timeReference[0], this.uInt32_),
89
        pack(this.bext.timeReference[1], this.uInt32_),
90
        pack(this.bext.version, this.uInt16_),
91
        this.io.writeString_(this.bext.UMID, 64),
92
        pack(this.bext.loudnessValue, this.uInt16_),
93
        pack(this.bext.loudnessRange, this.uInt16_),
94
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
95
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
96
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
97
        this.io.writeString_(this.bext.reserved, 180),
98
        this.io.writeString_(
99
          this.bext.codingHistory, this.bext.codingHistory.length));
100
    }
101
    return bytes;
102
  }
103
104
  /**
105
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
106
   * @private
107
   */
108
  enforceBext_() {
109
    for (var prop in this.bext) {
110
      if (this.bext.hasOwnProperty(prop)) {
111
        if (this.bext[prop] && prop != 'timeReference') {
112
          this.bext.chunkId = 'bext';
113
          break;
114
        }
115
      }
116
    }
117
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
118
      this.bext.chunkId = 'bext';
119
    }
120
  }
121
122
  /**
123
   * Return the bytes of the 'ds64' chunk.
124
   * @return {!Array<number>} The 'ds64' chunk bytes.
125
   * @private
126
   */
127
  getDs64Bytes_() {
128
    /** @type {!Array<number>} */
129
    let bytes = [];
130
    if (this.ds64.chunkId) {
131
      bytes = bytes.concat(
132
        packString(this.ds64.chunkId),
133
        pack(this.ds64.chunkSize, this.uInt32_),
134
        pack(this.ds64.riffSizeHigh, this.uInt32_),
135
        pack(this.ds64.riffSizeLow, this.uInt32_),
136
        pack(this.ds64.dataSizeHigh, this.uInt32_),
137
        pack(this.ds64.dataSizeLow, this.uInt32_),
138
        pack(this.ds64.originationTime, this.uInt32_),
139
        pack(this.ds64.sampleCountHigh, this.uInt32_),
140
        pack(this.ds64.sampleCountLow, this.uInt32_));
141
    }
142
    //if (this.ds64.tableLength) {
143
    //  ds64Bytes = ds64Bytes.concat(
144
    //    pack(this.ds64.tableLength, this.uInt32_),
145
    //    this.ds64.table);
146
    //}
147
    return bytes;
148
  }
149
150
  /**
151
   * Return the bytes of the 'cue ' chunk.
152
   * @return {!Array<number>} The 'cue ' chunk bytes.
153
   * @private
154
   */
155
  getCueBytes_() {
156
    /** @type {!Array<number>} */
157
    let bytes = [];
158
    if (this.cue.chunkId) {
159
      /** @type {!Array<number>} */
160
      let cuePointsBytes = this.getCuePointsBytes_();
161
      bytes = bytes.concat(
162
        packString(this.cue.chunkId),
163
        pack(cuePointsBytes.length + 4, this.uInt32_),
164
        pack(this.cue.dwCuePoints, this.uInt32_),
165
        cuePointsBytes);
166
    }
167
    return bytes;
168
  }
169
170
  /**
171
   * Return the bytes of the 'cue ' points.
172
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
173
   * @private
174
   */
175
  getCuePointsBytes_() {
176
    /** @type {!Array<number>} */
177
    let points = [];
178
    for (let i=0; i<this.cue.dwCuePoints; i++) {
179
      points = points.concat(
180
        pack(this.cue.points[i].dwName, this.uInt32_),
181
        pack(this.cue.points[i].dwPosition, this.uInt32_),
182
        packString(this.cue.points[i].fccChunk),
183
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
184
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
185
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
186
    }
187
    return points;
188
  }
189
190
  /**
191
   * Return the bytes of the 'smpl' chunk.
192
   * @return {!Array<number>} The 'smpl' chunk bytes.
193
   * @private
194
   */
195
  getSmplBytes_() {
196
    /** @type {!Array<number>} */
197
    let bytes = [];
198
    if (this.smpl.chunkId) {
199
      /** @type {!Array<number>} */
200
      let smplLoopsBytes = this.getSmplLoopsBytes_();
201
      bytes = bytes.concat(
202
        packString(this.smpl.chunkId),
203
        pack(smplLoopsBytes.length + 36, this.uInt32_),
204
        pack(this.smpl.dwManufacturer, this.uInt32_),
205
        pack(this.smpl.dwProduct, this.uInt32_),
206
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
207
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
208
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
209
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
210
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
211
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
212
        pack(this.smpl.dwSamplerData, this.uInt32_),
213
        smplLoopsBytes);
214
    }
215
    return bytes;
216
  }
217
218
  /**
219
   * Return the bytes of the 'smpl' loops.
220
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
221
   * @private
222
   */
223
  getSmplLoopsBytes_() {
224
    /** @type {!Array<number>} */
225
    let loops = [];
226
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
227
      loops = loops.concat(
228
        pack(this.smpl.loops[i].dwName, this.uInt32_),
229
        pack(this.smpl.loops[i].dwType, this.uInt32_),
230
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
231
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
232
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
233
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
234
    }
235
    return loops;
236
  }
237
238
  /**
239
   * Return the bytes of the 'fact' chunk.
240
   * @return {!Array<number>} The 'fact' chunk bytes.
241
   * @private
242
   */
243
  getFactBytes_() {
244
    /** @type {!Array<number>} */
245
    let bytes = [];
246
    if (this.fact.chunkId) {
247
      bytes = bytes.concat(
248
        packString(this.fact.chunkId),
249
        pack(this.fact.chunkSize, this.uInt32_),
250
        pack(this.fact.dwSampleLength, this.uInt32_));
251
    }
252
    return bytes;
253
  }
254
255
  /**
256
   * Return the bytes of the 'fmt ' chunk.
257
   * @return {!Array<number>} The 'fmt' chunk bytes.
258
   * @throws {Error} if no 'fmt ' chunk is present.
259
   * @private
260
   */
261
  getFmtBytes_() {
262
    /** @type {!Array<number>} */
263
    let fmtBytes = [];
264
    if (this.fmt.chunkId) {
265
      return fmtBytes.concat(
266
        packString(this.fmt.chunkId),
267
        pack(this.fmt.chunkSize, this.uInt32_),
268
        pack(this.fmt.audioFormat, this.uInt16_),
269
        pack(this.fmt.numChannels, this.uInt16_),
270
        pack(this.fmt.sampleRate, this.uInt32_),
271
        pack(this.fmt.byteRate, this.uInt32_),
272
        pack(this.fmt.blockAlign, this.uInt16_),
273
        pack(this.fmt.bitsPerSample, this.uInt16_),
274
        this.getFmtExtensionBytes_());
275
    }
276
    throw Error('Could not find the "fmt " chunk');
277
  }
278
279
  /**
280
   * Return the bytes of the fmt extension fields.
281
   * @return {!Array<number>} The fmt extension bytes.
282
   * @private
283
   */
284
  getFmtExtensionBytes_() {
285
    /** @type {!Array<number>} */
286
    let extension = [];
287
    if (this.fmt.chunkSize > 16) {
288
      extension = extension.concat(
289
        pack(this.fmt.cbSize, this.uInt16_));
290
    }
291
    if (this.fmt.chunkSize > 18) {
292
      extension = extension.concat(
293
        pack(this.fmt.validBitsPerSample, this.uInt16_));
294
    }
295
    if (this.fmt.chunkSize > 20) {
296
      extension = extension.concat(
297
        pack(this.fmt.dwChannelMask, this.uInt32_));
298
    }
299
    if (this.fmt.chunkSize > 24) {
300
      extension = extension.concat(
301
        pack(this.fmt.subformat[0], this.uInt32_),
302
        pack(this.fmt.subformat[1], this.uInt32_),
303
        pack(this.fmt.subformat[2], this.uInt32_),
304
        pack(this.fmt.subformat[3], this.uInt32_));
305
    }
306
    return extension;
307
  }
308
309
  /**
310
   * Return the bytes of the 'LIST' chunk.
311
   * @return {!Array<number>} The 'LIST' chunk bytes.
312
   */
313
  getLISTBytes_() {
314
    /** @type {!Array<number>} */
315
    let bytes = [];
316
    for (let i=0; i<this.LIST.length; i++) {
317
      /** @type {!Array<number>} */
318
      let subChunksBytes = this.getLISTSubChunksBytes_(
319
          this.LIST[i].subChunks, this.LIST[i].format);
320
      bytes = bytes.concat(
321
        packString(this.LIST[i].chunkId),
322
        pack(subChunksBytes.length + 4, this.uInt32_),
323
        packString(this.LIST[i].format),
324
        subChunksBytes);
325
    }
326
    return bytes;
327
  }
328
329
  /**
330
   * Return the bytes of the sub chunks of a 'LIST' chunk.
331
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
332
   * @param {string} format The format of the 'LIST' chunk.
333
   *    Currently supported values are 'adtl' or 'INFO'.
334
   * @return {!Array<number>} The sub chunk bytes.
335
   * @private
336
   */
337
  getLISTSubChunksBytes_(subChunks, format) {
338
    /** @type {!Array<number>} */
339
    let bytes = [];
340
    for (let i=0; i<subChunks.length; i++) {
341
      if (format == 'INFO') {
342
        bytes = bytes.concat(
343
          packString(subChunks[i].chunkId),
344
          pack(subChunks[i].value.length + 1, this.uInt32_),
345
          this.io.writeString_(
346
            subChunks[i].value, subChunks[i].value.length));
347
        bytes.push(0);
348
      } else if (format == 'adtl') {
349
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
350
          bytes = bytes.concat(
351
            packString(subChunks[i].chunkId),
352
            pack(
353
              subChunks[i].value.length + 4 + 1, this.uInt32_),
354
            pack(subChunks[i].dwName, this.uInt32_),
355
            this.io.writeString_(
356
              subChunks[i].value,
357
              subChunks[i].value.length));
358
          bytes.push(0);
359
        } else if (subChunks[i].chunkId == 'ltxt') {
360
          bytes = bytes.concat(
361
            this.getLtxtChunkBytes_(subChunks[i]));
362
        }
363
      }
364
      if (bytes.length % 2) {
365
        bytes.push(0);
366
      }
367
    }
368
    return bytes;
369
  }
370
371
  /**
372
   * Return the bytes of a 'ltxt' chunk.
373
   * @param {!Object} ltxt the 'ltxt' chunk.
374
   * @return {!Array<number>} The 'ltxt' chunk bytes.
375
   * @private
376
   */
377
  getLtxtChunkBytes_(ltxt) {
378
    return [].concat(
379
      packString(ltxt.chunkId),
380
      pack(ltxt.value.length + 20, this.uInt32_),
381
      pack(ltxt.dwName, this.uInt32_),
382
      pack(ltxt.dwSampleLength, this.uInt32_),
383
      pack(ltxt.dwPurposeID, this.uInt32_),
384
      pack(ltxt.dwCountry, this.uInt16_),
385
      pack(ltxt.dwLanguage, this.uInt16_),
386
      pack(ltxt.dwDialect, this.uInt16_),
387
      pack(ltxt.dwCodePage, this.uInt16_),
388
      this.io.writeString_(ltxt.value, ltxt.value.length));
389
  }
390
391
  /**
392
   * Return the bytes of the 'junk' chunk.
393
   * @return {!Array<number>} The 'junk' chunk bytes.
394
   * @private
395
   */
396
  getJunkBytes_() {
397
    /** @type {!Array<number>} */
398
    let bytes = [];
399
    if (this.junk.chunkId) {
400
      return bytes.concat(
401
        packString(this.junk.chunkId),
402
        pack(this.junk.chunkData.length, this.uInt32_),
403
        this.junk.chunkData);
404
    }
405
    return bytes;
406
  }
407
408
  /**
409
   * Return 'RIFF' if the container is 'RF64', the current container name
410
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
411
   * @return {string}
412
   * @private
413
   */
414
  correctContainer_() {
415
    return this.container == 'RF64' ? 'RIFF' : this.container;
416
  }
417
418
  /**
419
   * Set the string code of the bit depth based on the 'fmt ' chunk.
420
   * @private
421
   */
422
  bitDepthFromFmt_() {
423
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
424
      this.bitDepth = '32f';
425
    } else if (this.fmt.audioFormat === 6) {
426
      this.bitDepth = '8a';
427
    } else if (this.fmt.audioFormat === 7) {
428
      this.bitDepth = '8m';
429
    } else {
430
      this.bitDepth = this.fmt.bitsPerSample.toString();
431
    }
432
  }
433
434
  /**
435
   * Return a .wav file byte buffer with the data from the WaveFile object.
436
   * The return value of this method can be written straight to disk.
437
   * @return {!Uint8Array} The wav file bytes.
438
   * @private
439
   */
440
  createWaveFile_() {
441
    /** @type {!Array<!Array<number>>} */
442
    let fileBody = [
443
      this.getJunkBytes_(),
444
      this.getDs64Bytes_(),
445
      this.getBextBytes_(),
446
      this.getFmtBytes_(),
447
      this.getFactBytes_(),
448
      packString(this.data.chunkId),
449
      pack(this.data.samples.length, this.uInt32_),
450
      this.data.samples,
451
      this.getCueBytes_(),
452
      this.getSmplBytes_(),
453
      this.getLISTBytes_()
454
    ];
455
    /** @type {number} */
456
    let fileBodyLength = 0;
457
    for (let i=0; i<fileBody.length; i++) {
458
      fileBodyLength += fileBody[i].length;
459
    }
460
    /** @type {!Uint8Array} */
461
    let file = new Uint8Array(fileBodyLength + 12);
462
    /** @type {number} */
463
    let index = 0;
464
    index = packStringTo(this.container, file, index);
465
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
466
    index = packStringTo(this.format, file, index);
467
    for (let i=0; i<fileBody.length; i++) {
468
      file.set(fileBody[i], index);
469
      index += fileBody[i].length;
470
    }
471
    return file;
472
  }
473
474
  /**
475
   * Update the type definition used to read and write the samples.
476
   * @private
477
   */
478
  updateDataType_() {
479
    /** @type {!Object} */
480
    this.dataType = {
481
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
482
      float: this.bitDepth == '32f' || this.bitDepth == '64',
483
      signed: this.bitDepth != '8',
484
      be: this.container == 'RIFX'
485
    };
486
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
487
      this.dataType.bits = 8;
488
      this.dataType.signed = false;
489
    }
490
  }
491
492
  /**
493
   * Set up the WaveFile object from a byte buffer.
494
   * @param {!Uint8Array} buffer The buffer.
495
   * @param {boolean=} samples True if the samples should be loaded.
496
   * @throws {Error} If container is not RIFF, RIFX or RF64.
497
   * @throws {Error} If no 'fmt ' chunk is found.
498
   * @throws {Error} If no 'data' chunk is found.
499
   */
500
  readWavBuffer(buffer, samples=true) {
501
    this.io.head_ = 0;
502
    this.clearHeader_();
503
    this.readRIFFChunk_(buffer);
504
    /** @type {!Object} */
505
    let chunk = riffChunks(buffer);
506
    this.readDs64Chunk_(buffer, chunk.subChunks);
507
    this.readFmtChunk_(buffer, chunk.subChunks);
508
    this.readFactChunk_(buffer, chunk.subChunks);
509
    this.readBextChunk_(buffer, chunk.subChunks);
510
    this.readCueChunk_(buffer, chunk.subChunks);
511
    this.readSmplChunk_(buffer, chunk.subChunks);
512
    this.readDataChunk_(buffer, chunk.subChunks, samples);
513
    this.readJunkChunk_(buffer, chunk.subChunks);
514
    this.readLISTChunk_(buffer, chunk.subChunks);
515
    this.bitDepthFromFmt_();
516
    this.updateDataType_();
517
  }
518
519
  /**
520
   * Return the closest greater number of bits for a number of bits that
521
   * do not fill a full sequence of bytes.
522
   * @param {string} bitDepthCode The bit depth.
523
   * @return {string}
524
   * @private
525
   */
526
  realBitDepth_(bitDepthCode) {
527
    if (bitDepthCode != '32f') {
528
      bitDepthCode = (((parseInt(bitDepthCode, 10) - 1) | 7) + 1).toString();
529
    }
530
    return bitDepthCode;
531
  }
532
533
  /**
534
   * Reset attributes that should emptied when a file is
535
   * created with the fromScratch() or fromBuffer() methods.
536
   * @private
537
   */
538
  clearHeader_() {
539
    this.fmt.cbSize = 0;
540
    this.fmt.validBitsPerSample = 0;
541
    this.fact.chunkId = '';
542
    this.ds64.chunkId = '';
543
  }
544
545
  /**
546
   * Set up to work wih big-endian or little-endian files.
547
   * The types used are changed to LE or BE. If the
548
   * the file is big-endian (RIFX), true is returned.
549
   * @return {boolean} True if the file is RIFX.
550
   * @private
551
   */
552
  LEorBE_() {
553
    /** @type {boolean} */
554
    let bigEndian = this.container === 'RIFX';
555
    this.uInt16_.be = bigEndian;
556
    this.uInt32_.be = bigEndian;
557
    return bigEndian;
558
  }
559
560
  /**
561
   * Find a chunk by its fourCC_ in a array of RIFF chunks.
562
   * @param {!Object} chunks The wav file chunks.
563
   * @param {string} chunkId The chunk fourCC_.
564
   * @param {boolean} multiple True if there may be multiple chunks
565
   *    with the same chunkId.
566
   * @return {?Array<!Object>}
567
   * @private
568
   */
569
  findChunk_(chunks, chunkId, multiple=false) {
570
    /** @type {!Array<!Object>} */
571
    let chunk = [];
572
    for (let i=0; i<chunks.length; i++) {
573
      if (chunks[i].chunkId == chunkId) {
574
        if (multiple) {
575
          chunk.push(chunks[i]);
576
        } else {
577
          return chunks[i];
578
        }
579
      }
580
    }
581
    if (chunkId == 'LIST') {
582
      return chunk.length ? chunk : null;
583
    }
584
    return null;
585
  }
586
587
  /**
588
   * Read the RIFF chunk a wave file.
589
   * @param {!Uint8Array} bytes A wav buffer.
590
   * @throws {Error} If no 'RIFF' chunk is found.
591
   * @private
592
   */
593
  readRIFFChunk_(bytes) {
594
    this.io.head_ = 0;
595
    this.container = this.io.readString_(bytes, 4);
596
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
597
      throw Error('Not a supported format.');
598
    }
599
    this.LEorBE_();
600
    this.chunkSize = this.io.read_(bytes, this.uInt32_);
601
    this.format = this.io.readString_(bytes, 4);
602
    if (this.format != 'WAVE') {
603
      throw Error('Could not find the "WAVE" format identifier');
604
    }
605
  }
606
607
  /**
608
   * Read the 'fmt ' chunk of a wave file.
609
   * @param {!Uint8Array} buffer The wav file buffer.
610
   * @param {!Object} signature The file signature.
611
   * @throws {Error} If no 'fmt ' chunk is found.
612
   * @private
613
   */
614
  readFmtChunk_(buffer, signature) {
615
    /** @type {?Object} */
616
    let chunk = this.findChunk_(signature, 'fmt ');
617
    if (chunk) {
618
      this.io.head_ = chunk.chunkData.start;
619
      this.fmt.chunkId = chunk.chunkId;
620
      this.fmt.chunkSize = chunk.chunkSize;
621
      this.fmt.audioFormat = this.io.read_(buffer, this.uInt16_);
622
      this.fmt.numChannels = this.io.read_(buffer, this.uInt16_);
623
      this.fmt.sampleRate = this.io.read_(buffer, this.uInt32_);
624
      this.fmt.byteRate = this.io.read_(buffer, this.uInt32_);
625
      this.fmt.blockAlign = this.io.read_(buffer, this.uInt16_);
626
      this.fmt.bitsPerSample = this.io.read_(buffer, this.uInt16_);
627
      this.readFmtExtension_(buffer);
628
    } else {
629
      throw Error('Could not find the "fmt " chunk');
630
    }
631
  }
632
633
  /**
634
   * Read the 'fmt ' chunk extension.
635
   * @param {!Uint8Array} buffer The wav file buffer.
636
   * @private
637
   */
638
  readFmtExtension_(buffer) {
639
    if (this.fmt.chunkSize > 16) {
640
      this.fmt.cbSize = this.io.read_(buffer, this.uInt16_);
641
      if (this.fmt.chunkSize > 18) {
642
        this.fmt.validBitsPerSample = this.io.read_(buffer, this.uInt16_);
643
        if (this.fmt.chunkSize > 20) {
644
          this.fmt.dwChannelMask = this.io.read_(buffer, this.uInt32_);
645
          this.fmt.subformat = [
646
            this.io.read_(buffer, this.uInt32_),
647
            this.io.read_(buffer, this.uInt32_),
648
            this.io.read_(buffer, this.uInt32_),
649
            this.io.read_(buffer, this.uInt32_)];
650
        }
651
      }
652
    }
653
  }
654
655
  /**
656
   * Read the 'fact' chunk of a wav file.
657
   * @param {!Uint8Array} buffer The wav file buffer.
658
   * @param {!Object} signature The file signature.
659
   * @private
660
   */
661
  readFactChunk_(buffer, signature) {
662
    /** @type {?Object} */
663
    let chunk = this.findChunk_(signature, 'fact');
664
    if (chunk) {
665
      this.io.head_ = chunk.chunkData.start;
666
      this.fact.chunkId = chunk.chunkId;
667
      this.fact.chunkSize = chunk.chunkSize;
668
      this.fact.dwSampleLength = this.io.read_(buffer, this.uInt32_);
669
    }
670
  }
671
672
  /**
673
   * Read the 'cue ' chunk of a wave file.
674
   * @param {!Uint8Array} buffer The wav file buffer.
675
   * @param {!Object} signature The file signature.
676
   * @private
677
   */
678
  readCueChunk_(buffer, signature) {
679
    /** @type {?Object} */
680
    let chunk = this.findChunk_(signature, 'cue ');
681
    if (chunk) {
682
      this.io.head_ = chunk.chunkData.start;
683
      this.cue.chunkId = chunk.chunkId;
684
      this.cue.chunkSize = chunk.chunkSize;
685
      this.cue.dwCuePoints = this.io.read_(buffer, this.uInt32_);
686
      for (let i=0; i<this.cue.dwCuePoints; i++) {
687
        this.cue.points.push({
688
          dwName: this.io.read_(buffer, this.uInt32_),
689
          dwPosition: this.io.read_(buffer, this.uInt32_),
690
          fccChunk: this.io.readString_(buffer, 4),
691
          dwChunkStart: this.io.read_(buffer, this.uInt32_),
692
          dwBlockStart: this.io.read_(buffer, this.uInt32_),
693
          dwSampleOffset: this.io.read_(buffer, this.uInt32_),
694
        });
695
      }
696
    }
697
  }
698
699
  /**
700
   * Read the 'smpl' chunk of a wave file.
701
   * @param {!Uint8Array} buffer The wav file buffer.
702
   * @param {!Object} signature The file signature.
703
   * @private
704
   */
705
  readSmplChunk_(buffer, signature) {
706
    /** @type {?Object} */
707
    let chunk = this.findChunk_(signature, 'smpl');
708
    if (chunk) {
709
      this.io.head_ = chunk.chunkData.start;
710
      this.smpl.chunkId = chunk.chunkId;
711
      this.smpl.chunkSize = chunk.chunkSize;
712
      this.smpl.dwManufacturer = this.io.read_(buffer, this.uInt32_);
713
      this.smpl.dwProduct = this.io.read_(buffer, this.uInt32_);
714
      this.smpl.dwSamplePeriod = this.io.read_(buffer, this.uInt32_);
715
      this.smpl.dwMIDIUnityNote = this.io.read_(buffer, this.uInt32_);
716
      this.smpl.dwMIDIPitchFraction = this.io.read_(buffer, this.uInt32_);
717
      this.smpl.dwSMPTEFormat = this.io.read_(buffer, this.uInt32_);
718
      this.smpl.dwSMPTEOffset = this.io.read_(buffer, this.uInt32_);
719
      this.smpl.dwNumSampleLoops = this.io.read_(buffer, this.uInt32_);
720
      this.smpl.dwSamplerData = this.io.read_(buffer, this.uInt32_);
721
      for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
722
        this.smpl.loops.push({
723
          dwName: this.io.read_(buffer, this.uInt32_),
724
          dwType: this.io.read_(buffer, this.uInt32_),
725
          dwStart: this.io.read_(buffer, this.uInt32_),
726
          dwEnd: this.io.read_(buffer, this.uInt32_),
727
          dwFraction: this.io.read_(buffer, this.uInt32_),
728
          dwPlayCount: this.io.read_(buffer, this.uInt32_),
729
        });
730
      }
731
    }
732
  }
733
734
  /**
735
   * Read the 'data' chunk of a wave file.
736
   * @param {!Uint8Array} buffer The wav file buffer.
737
   * @param {!Object} signature The file signature.
738
   * @param {boolean} samples True if the samples should be loaded.
739
   * @throws {Error} If no 'data' chunk is found.
740
   * @private
741
   */
742
  readDataChunk_(buffer, signature, samples) {
743
    /** @type {?Object} */
744
    let chunk = this.findChunk_(signature, 'data');
745
    if (chunk) {
746
      this.data.chunkId = 'data';
747
      this.data.chunkSize = chunk.chunkSize;
748
      if (samples) {
749
        this.data.samples = buffer.slice(
750
          chunk.chunkData.start,
751
          chunk.chunkData.end);
752
      }
753
    } else {
754
      throw Error('Could not find the "data" chunk');
755
    }
756
  }
757
758
  /**
759
   * Read the 'bext' chunk of a wav file.
760
   * @param {!Uint8Array} buffer The wav file buffer.
761
   * @param {!Object} signature The file signature.
762
   * @private
763
   */
764
  readBextChunk_(buffer, signature) {
765
    /** @type {?Object} */
766
    let chunk = this.findChunk_(signature, 'bext');
767
    if (chunk) {
768
      this.io.head_ = chunk.chunkData.start;
769
      this.bext.chunkId = chunk.chunkId;
770
      this.bext.chunkSize = chunk.chunkSize;
771
      this.bext.description = this.io.readString_(buffer, 256);
772
      this.bext.originator = this.io.readString_(buffer, 32);
773
      this.bext.originatorReference = this.io.readString_(buffer, 32);
774
      this.bext.originationDate = this.io.readString_(buffer, 10);
775
      this.bext.originationTime = this.io.readString_(buffer, 8);
776
      this.bext.timeReference = [
777
        this.io.read_(buffer, this.uInt32_),
778
        this.io.read_(buffer, this.uInt32_)];
779
      this.bext.version = this.io.read_(buffer, this.uInt16_);
780
      this.bext.UMID = this.io.readString_(buffer, 64);
781
      this.bext.loudnessValue = this.io.read_(buffer, this.uInt16_);
782
      this.bext.loudnessRange = this.io.read_(buffer, this.uInt16_);
783
      this.bext.maxTruePeakLevel = this.io.read_(buffer, this.uInt16_);
784
      this.bext.maxMomentaryLoudness = this.io.read_(buffer, this.uInt16_);
785
      this.bext.maxShortTermLoudness = this.io.read_(buffer, this.uInt16_);
786
      this.bext.reserved = this.io.readString_(buffer, 180);
787
      this.bext.codingHistory = this.io.readString_(
788
        buffer, this.bext.chunkSize - 602);
789
    }
790
  }
791
792
  /**
793
   * Read the 'ds64' chunk of a wave file.
794
   * @param {!Uint8Array} buffer The wav file buffer.
795
   * @param {!Object} signature The file signature.
796
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
797
   * @private
798
   */
799
  readDs64Chunk_(buffer, signature) {
800
    /** @type {?Object} */
801
    let chunk = this.findChunk_(signature, 'ds64');
802
    if (chunk) {
803
      this.io.head_ = chunk.chunkData.start;
804
      this.ds64.chunkId = chunk.chunkId;
805
      this.ds64.chunkSize = chunk.chunkSize;
806
      this.ds64.riffSizeHigh = this.io.read_(buffer, this.uInt32_);
807
      this.ds64.riffSizeLow = this.io.read_(buffer, this.uInt32_);
808
      this.ds64.dataSizeHigh = this.io.read_(buffer, this.uInt32_);
809
      this.ds64.dataSizeLow = this.io.read_(buffer, this.uInt32_);
810
      this.ds64.originationTime = this.io.read_(buffer, this.uInt32_);
811
      this.ds64.sampleCountHigh = this.io.read_(buffer, this.uInt32_);
812
      this.ds64.sampleCountLow = this.io.read_(buffer, this.uInt32_);
813
      //if (this.ds64.chunkSize > 28) {
814
      //  this.ds64.tableLength = unpack(
815
      //    chunkData.slice(28, 32), this.uInt32_);
816
      //  this.ds64.table = chunkData.slice(
817
      //     32, 32 + this.ds64.tableLength); 
818
      //}
819
    } else {
820
      if (this.container == 'RF64') {
821
        throw Error('Could not find the "ds64" chunk');  
822
      }
823
    }
824
  }
825
826
  /**
827
   * Read the 'LIST' chunks of a wave file.
828
   * @param {!Uint8Array} buffer The wav file buffer.
829
   * @param {!Object} signature The file signature.
830
   * @private
831
   */
832
  readLISTChunk_(buffer, signature) {
833
    /** @type {?Object} */
834
    let listChunks = this.findChunk_(signature, 'LIST', true);
835
    if (listChunks === null) {
836
      return;
837
    }
838
    for (let j=0; j < listChunks.length; j++) {
839
      /** @type {!Object} */
840
      let subChunk = listChunks[j];
841
      this.LIST.push({
842
        chunkId: subChunk.chunkId,
843
        chunkSize: subChunk.chunkSize,
844
        format: subChunk.format,
845
        subChunks: []});
846
      for (let x=0; x<subChunk.subChunks.length; x++) {
847
        this.readLISTSubChunks_(subChunk.subChunks[x],
848
          subChunk.format, buffer);
849
      }
850
    }
851
  }
852
853
  /**
854
   * Read the sub chunks of a 'LIST' chunk.
855
   * @param {!Object} subChunk The 'LIST' subchunks.
856
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
857
   * @param {!Uint8Array} buffer The wav file buffer.
858
   * @private
859
   */
860
  readLISTSubChunks_(subChunk, format, buffer) {
861
    if (format == 'adtl') {
862
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
863
        this.io.head_ = subChunk.chunkData.start;
864
        /** @type {!Object<string, string|number>} */
865
        let item = {
866
          chunkId: subChunk.chunkId,
867
          chunkSize: subChunk.chunkSize,
868
          dwName: this.io.read_(buffer, this.uInt32_)
869
        };
870
        if (subChunk.chunkId == 'ltxt') {
871
          item.dwSampleLength = this.io.read_(buffer, this.uInt32_);
872
          item.dwPurposeID = this.io.read_(buffer, this.uInt32_);
873
          item.dwCountry = this.io.read_(buffer, this.uInt16_);
874
          item.dwLanguage = this.io.read_(buffer, this.uInt16_);
875
          item.dwDialect = this.io.read_(buffer, this.uInt16_);
876
          item.dwCodePage = this.io.read_(buffer, this.uInt16_);
877
        }
878
        item.value = this.io.readZSTR_(buffer, this.io.head_);
879
        this.LIST[this.LIST.length - 1].subChunks.push(item);
880
      }
881
    // RIFF INFO tags like ICRD, ISFT, ICMT
882
    } else if(format == 'INFO') {
883
      this.io.head_ = subChunk.chunkData.start;
884
      this.LIST[this.LIST.length - 1].subChunks.push({
885
        chunkId: subChunk.chunkId,
886
        chunkSize: subChunk.chunkSize,
887
        value: this.io.readZSTR_(buffer, this.io.head_)
888
      });
889
    }
890
  }
891
892
  /**
893
   * Read the 'junk' chunk of a wave file.
894
   * @param {!Uint8Array} buffer The wav file buffer.
895
   * @param {!Object} signature The file signature.
896
   * @private
897
   */
898
  readJunkChunk_(buffer, signature) {
899
    /** @type {?Object} */
900
    let chunk = this.findChunk_(signature, 'junk');
901
    if (chunk) {
902
      this.junk = {
903
        chunkId: chunk.chunkId,
904
        chunkSize: chunk.chunkSize,
905
        chunkData: [].slice.call(buffer.slice(
906
          chunk.chunkData.start,
907
          chunk.chunkData.end))
908
      };
909
    }
910
  }
911
}
912